-- Written by Pedro Gimeno.
-- This work is in the public domain if your jurisdiction allows it.
-- If not, the author allows anyone to do anything with this work, to the
-- maximum extent allowed by law.

if not love.graphics.isActive then
  function love.graphics.isActive()
    return true
  end
end


local fbcanvas
local script
local started = false
local corosc -- coroutine for the script
local huge = math.huge
local wait = {}
local peek = {}
local curEventName
local curEventData = {0}
local lg = love.graphics
local timerTime = huge
local sleepTime = huge
local saveCanvas = false


local function pack_n(...)
  curEventData[1] = select('#', ...)
  for i = 1, curEventData[1] do
    curEventData[i + 1] = (select(i, ...))
  end
end


local unpack_n
do
  local function unpack_count(x, n)
    if n == x[1] + 1 then
      return
    end
    n = n + 1
    return x[n], unpack_count(x, n)
  end

  function unpack_n(a)
    return unpack_count(a, 1)
  end
end


local function nullFunc() end


local function handleEvent(event, ...)
  if event == "update" then
    timerTime = timerTime - ...
    sleepTime = sleepTime - ...
    if sleepTime <= 0 then
      sleepTime = huge
      curEventName = "sleep"
      assert(coroutine.resume(corosc))
    end
    if timerTime <= 0 then
      timerTime = huge
      handleEvent("timer")
    end
  end
  curEventName = event
  pack_n(...)
  if coroutine.status(corosc) == "suspended" then
    return select(2, assert(coroutine.resume(corosc)))
  end
end


-- Handlers not in love.handlers
function love.load(...)
  handleEvent("load", ...)
  -- Hack: love.event.pump() can't work with a canvas active.
  -- The first time it's executed is immediately after this.
  if love.graphics.isActive() then
    saveCanvas = love.graphics.getCanvas()
    love.graphics.setCanvas()
  else
    saveCanvas = nil -- shouldn't be false
  end
end


do
  local savePump = love.event.pump

  -- Replace love.event.pump to restore the canvas
  local function afterPump(...)
    if saveCanvas ~= false then
      if love.graphics.isActive() then
        love.graphics.setCanvas(saveCanvas)
        saveCanvas = false  -- This distinguishes pump() calls from love.run
                            -- from pump() calls from user code.
      end
      -- if we saved it, it means we're in a new frame
      handleEvent("newframe")
    end
    return ...
  end

  function love.event.pump(...)
    return afterPump(savePump(...))
  end


  -- Replace love.graphics.setMode to (re)create the canvas and activate it
  local origSetMode = love.window.setMode
  function love.window.setMode(...)
    if fbcanvas then
      if love.graphics.isActive() then
        love.graphics.setCanvas()
      end
      fbcanvas:release()
    end
    origSetMode(...)
    fbcanvas = love.graphics.newCanvas()
    _G.fbcanvas = fbcanvas
    love.graphics.clear(0, 0, 0, 255)
    love.graphics.setCanvas(fbcanvas)
  end


  local origClear
  function love.update(...)
    handleEvent("update", ...)

    if love.graphics.isActive() then  -- love.draw isn't called otherwise
      -- To prevent the internal call to love.graphics.clear from clearing the
      -- framebuffer, we patch it here to do nothing.
      origClear = love.graphics.clear
      love.graphics.clear = nullFunc
      love.graphics.push()
    end
  end


  function love.draw(...)
    love.graphics.clear = origClear
    saveCanvas = love.graphics.getCanvas()
    love.graphics.setCanvas()
    local r,g,b,a = love.graphics.getColor()
    local rgbmode, alphamode = love.graphics.getBlendMode()
    love.graphics.setColor(255, 255, 255, 255)
    love.graphics.setBlendMode("replace", "premultiplied")
    love.graphics.draw(fbcanvas)
    love.graphics.setBlendMode(rgbmode, alphamode)
    love.graphics.setColor(r, g, b, a)
    love.graphics.pop()

    -- Notify about the event. Unfortunately we have to restore the canvas;
    -- otherwise stuff drawn by user code during this event will be lost.
    love.graphics.setCanvas(saveCanvas)
    handleEvent("draw")
    saveCanvas = love.graphics.getCanvas()
    love.graphics.setCanvas() -- for present/pump
  end
end


local function waitEvent(event, ...)
  coroutine.yield(...)
  while curEventName ~= event do
    coroutine.yield()
  end
  return unpack_n(curEventData)
end


local function peekEvent(event)
  if event == curEventName then
    return true, unpack_n(curEventData)
  end
end


local function poll(...)
  coroutine.yield(...)
end


local function quit()
  love.event.quit()
  waitEvent("quit")
  coroutine.yield()
end


local function getEvent(...)
  poll(...)
  return curEventName, unpack_n(curEventData)
end


local function startTimer(time)
  timerTime = time + 0
end


local function pause(time, ...)
  if time == nil then
    time = huge
  end
  sleepTime = time + 0
  poll(...)
  repeat
    local ret, k = peekEvent("keypressed")
    if k == "escape" then
      quit()
    end
    if ret then
      break
    end
    if peekEvent("mousepressed") then
      break
    end
    if curEventName =="sleep" then
      break
    end
    poll()
  until false
  sleepTime = huge
end


local function sleep(time)
  sleepTime = time + 0
  waitEvent("sleep")
end


-- Assign the default love2d handlers
for k in next, love.handlers do
  love[k] = function(...)
    return handleEvent(k, ...)
  end
  wait[k] = function () return waitEvent(k) end
  peek[k] = function () return peekEvent(k) end
end

-- Special events
wait.load = function () return waitEvent("load") end
peek.load = function () return peekEvent("load") end
wait.update = function () return waitEvent("update") end
peek.update = function () return peekEvent("update") end
wait.draw = function () return waitEvent("draw") end
peek.draw = function () return peekEvent("draw") end
wait.newframe = function () return waitEvent("newframe") end
peek.newframe = function () return peekEvent("newframe") end
wait.timer = function () return waitEvent("timer") end
peek.timer = function () return peekEvent("timer") end

-- Special functions that aren't events
wait.event = waitEvent
peek.event = peekEvent

-- Set the API globals
_G.fbcanvas = fbcanvas
_G.wait = wait
_G.peek = peek
_G.sleep = sleep
_G.pause = pause
_G.event = getEvent
_G.poll = poll
_G.timer = startTimer
_G.quit = quit


-- Load the script
do
  local scriptName = arg[2] or 'mycode.lua'
  script = love.filesystem.load(arg[love.filesystem.isFused() and 1 or 2] or 'mycode.lua')

  if script == nil then
    love.window.showMessageBox("Error: Script not found",
      "Script not found: " .. scriptName, "error", false)
  end
end

-- Prepare the coroutine and start it
corosc = coroutine.create(script)

assert(coroutine.resume(corosc))
